Skip to content

Conversation

@noahgorstein
Copy link
Member

@noahgorstein noahgorstein commented Oct 22, 2024

Overview

  • Adds stardog.cloud subpackage containing modules for working with the Stardog Cloud API
    • The main entrypoint for interacting with Stardog Cloud is creating and using either the synchronous stardog.cloud.client.Client or asynchronous stardog.cloud.client.AsyncClient.
    • The design I'm after is that the client's have "services" attached to them like VoiceboxApp which is the entrypoint for working with Voicebox. This pattern should be able to be extended if and when we extend the public api to contain non voicebox related apis.
    • Reworks the docs to include Stardog Cloud: See the docs associated with this PR https://pystardog--176.org.readthedocs.build/en/176/index.html
    • Adds an example of working with the new cloud api. See https://github.com/stardog-union/pystardog/blob/cloud-api/examples/voicebox_example.py
      • I also added a vhs tape and the gif generated from it. I linked that to the readme and docs:
        voicebox_demo

API Usage

Example using synchronous client:

import json
import os
import time

from stardog.cloud import Client
from stardog.cloud.exceptions import RateLimitExceededException, StardogCloudException

app_api_token = os.getenv("VOICEBOX_APP_TOKEN")

try:
    results = {}
    with Client(base_url="https://portal.stardoglabs.com/api") as client:
        program_start = time.time()
        app = client.voicebox_app(
            app_api_token=app_api_token, client_id="your-client-id"
        )
        for i in range(2):
            start = time.time()
            response = app.ask(question="How many sensors are there?")
            end = time.time()
            results[i] = {**response.model_dump(), "response_time": f"{end-start:.2f}"}
        program_end = time.time()
        print(json.dumps(results, indent=2))
        print(f"Total time taken: {program_end-program_start:.2f}")

except RateLimitExceededException as e:
    print("Rate limit error.")
except StardogCloudException as e:
    print(f"Error occurred: {e}")

Output:

{
  "0": {
    "content": "There are **27** sensors.",
    "conversation_id": "b714010d-a244-4268-bd59-ade25c37d0b3",
    "message_id": "634fa80d-20c6-4f9f-b104-29e3b0956cbf",
    "interpreted_question": "How many sensors are there?",
    "sparql_query": "# How many sensors are there?\n\nSELECT DISTINCT (COUNT( ?sensor0) AS  ?sensorCount) \nWHERE {\n   ?sensor0 a scm:Sensor . \n}",
    "response_time": "2.45"
  },
  "1": {
    "content": "There are **27** sensors.",
    "conversation_id": "51cbbf18-7d2f-4621-854b-0d5f49f6c13e",
    "message_id": "c134eb1c-850a-4ff2-a34a-fed194fe83ac",
    "interpreted_question": "How many sensors are there?",
    "sparql_query": "# How many sensors are there?\n\nSELECT DISTINCT (COUNT( ?sensor0) AS  ?sensorCount) \nWHERE {\n   ?sensor0 a scm:Sensor . \n}",
    "response_time": "2.01"
  }
}
Total time taken: 4.45

Example Using asynchronous AsyncClient:

import asyncio
import json
import os
import time

from stardog.cloud import AsyncClient
from stardog.cloud.exceptions import RateLimitExceededException, StardogCloudException
from stardog.cloud.voicebox import VoiceboxApp

app_api_token = os.getenv("VOICEBOX_APP_TOKEN")

results = {}


async def ask_and_persist(app: VoiceboxApp, index: int, question: str):
    start = time.time()
    answer = await app.async_ask(question)
    print(answer)
    end = time.time()
    results[index] = {**answer.model_dump(), "response_time": f"{end-start:.2f}"}


async def benchmark():
    try:
        async with AsyncClient(base_url="https://portal.stardoglabs.com/api") as client:
            app = client.voicebox_app(
                app_api_token=app_api_token, client_id="your-client-id"
            )
            tasks = [
                ask_and_persist(app, i, "How many sensors are there?") for i in range(2)
            ]
            program_start = time.time()
            await asyncio.gather(*tasks)
            program_end = time.time()
            print(json.dumps(results, indent=2))
            print(f"Total time taken: {program_end-program_start:.2f}")

    except RateLimitExceededException as e:
        print("Rate limit error.")
    except StardogCloudException as e:
        print(f"Error occurred: {e}")


asyncio.run(benchmark())

Output:

{
  "0": {
    "content": "There are **27** sensors.",
    "conversation_id": "c8e583d8-d5cd-4ea9-97a5-9c6055f64719",
    "message_id": "054c6c99-58c8-42b8-9366-45b1fcf8d728",
    "interpreted_question": "How many sensors are there?",
    "sparql_query": "# How many sensors are there?\n\nSELECT DISTINCT (COUNT( ?sensor0) AS  ?sensorCount) \nWHERE {\n   ?sensor0 a scm:Sensor . \n}",
    "response_time": "2.66"
  },
  "1": {
    "content": "There are **27** sensors.",
    "conversation_id": "22459fd5-35ef-44c4-80cd-26b5a71eb7c1",
    "message_id": "a30683d2-9535-4d57-a957-df25894d30f5",
    "interpreted_question": "How many sensors are there?",
    "sparql_query": "# How many sensors are there?\n\nSELECT DISTINCT (COUNT( ?sensor0) AS  ?sensorCount) \nWHERE {\n   ?sensor0 a scm:Sensor . \n}",
    "response_time": "2.66"
  }
}
Total time taken: 2.67

@noahgorstein noahgorstein changed the title feat: add APIs for working with Stardog Cloud public API VBX-292: add APIs for working with Stardog Cloud public API Jul 29, 2025
noahgorstein and others added 17 commits July 30, 2025 14:13
Remove redundant descriptions and verbose explanations to improve readability.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add local Stardog logo and configure in Sphinx
- Update theme styling with custom nav header background
- Remove external logo image from index
- Remove redundant docs README
Remove detailed installation, examples, and contributing sections that are now comprehensively covered in the documentation. Replace with concise overview and clear links to specific doc sections.
Update description in pyproject.toml to reflect that pystardog supports both Stardog servers and Stardog Cloud functionality.
Add respx library for HTTP request mocking in tests
- Add abstract base_url property to BaseClient
- Implement base_url property in Client and AsyncClient
- Fix StardogCloudAPIEndpoints enum usage with .value
- Use base_url property instead of internal _client.base_url
- Test with StardogCloudAPIEndpoints enum values
- Replace custom MockClient with real Client/AsyncClient instances
- Use respx library for HTTP request mocking
- Split tests into sync and async test classes
- Verify actual HTTP headers and request behavior
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved a lot of this content to the readthedocs site.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some doc cleanup included here too while I was adding the new docs for cloud.

@noahgorstein noahgorstein marked this pull request as ready for review August 5, 2025 13:06
Copy link
Member

@mhgrove mhgrove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some non-code changes. Code looks good, tests ran locally.

"""Whether Voicebox should use reasoning or not in its queries to the Stardog database."""


class VoiceboxAction(BaseModel):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this correspond to?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the actions returned in the api response for ask and generate-query.


@computed_field # type: ignore[misc]
@property
def interpreted_question(self) -> Optional[str]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i know the front end uses this now, but it doesn't exist w/ v3

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v3 isn't exposed in the public API yet.

noahgorstein and others added 4 commits August 8, 2025 13:04
Co-authored-by: Michael Grove <mhgrove@users.noreply.github.com>
Co-authored-by: Michael Grove <mhgrove@users.noreply.github.com>
Co-authored-by: Michael Grove <mhgrove@users.noreply.github.com>
Co-authored-by: Michael Grove <mhgrove@users.noreply.github.com>
Copy link
Member

@mhgrove mhgrove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@noahgorstein noahgorstein merged commit 3962489 into main Aug 8, 2025
4 checks passed
@noahgorstein noahgorstein deleted the cloud-api branch August 8, 2025 17:25
@pranav-k
Copy link

Just went through the project and changes - looks good!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants